﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;
using System.Threading;

namespace MultithreadFileCopy
{
    public class MultithreadCopySystem : IEnumerable<MultithreadFile>
    {
        private List<MultithreadFile> files;

        public MultithreadCopySystem()
        {
            files = new List<MultithreadFile>();
            BufferSize = 1024;

            OnError += delegate { };
            OnProcessCompleted += delegate { };
            OnProgressReport += delegate { };
        }

        public void Add(MultithreadFile FILE)
        {
            if (files.Where(z => z.DestinationPath.ToLower() == FILE.DestinationPath.ToLower()).Count() > 0)
            {
                OnError(this, new MultithreadFileCopyErrorArgs()
                {
                    Error = "File destination already on list",
                });
                return;
            }
            files.Add(FILE);
        }
        public void Remove(MultithreadFile FILE)
        {
            if (files.Contains(FILE))
                files.Remove(FILE);
        }

        public void Start()
        {
            Noargs process = new Noargs(ThreadProcess);
            process.BeginInvoke(null, null);
        }
        public void ThreadProcess()
        {
            int CurrentFile = 0;
            long CompletedBytes = 0;
            long FileCompletedBytes = 0;

            #region CopyProcess
            for (int i = 0; i < files.Count; i++)
            {
                try
                {
                    CurrentFile++;
                    FileCompletedBytes = 0;

                    #region Overwrite
                    if (File.Exists(files[i].DestinationPath))
                    {
                        if (!files[i].Overwrite)
                        {
                            CompletedBytes += files[i].FileSize;
                            FileCompletedBytes = files[i].FileSize;

                            files[i].AddLog("Destination allready exist");
                            files[i].SetStatus(FileStatus.Skiped);

                            OnProgressReport(this, new ProgressReportArgs()
                            {
                                CompletedBytes = CompletedBytes,
                                CurrentFile = CurrentFile,
                                Destination = files[i].DestinationPath,
                                Source = files[i].SourcePath,
                                FileCompletedBytes = FileCompletedBytes,
                                FileTotalBytes = files[i].FileSize,
                                TotalFiles = files.Count,
                                TotalBytes = TotalSize,
                            });
                            continue;
                        }
                        else
                        {
                            try
                            {
                                File.Delete(files[i].DestinationPath);
                            }
                            catch (Exception ex)
                            {
                                files[i].SetStatus(FileStatus.Error);

                                files[i].AddLog("ERROR |Error deleting destination| ->");
                                files[i].AddLog(ex.ToString());

                                CompletedBytes -= FileCompletedBytes;
                                CompletedBytes += files[i].FileSize;

                                OnError(this, new MultithreadFileCopyErrorArgs()
                                {
                                    Error = string.Format("Error deleting destination: {1}", files[i].SourcePath, files[i].DestinationPath),
                                    Description = ex.ToString(),
                                });
                                continue;
                            }
                        }
                    }
                    #endregion

                    files[i].SetStatus(FileStatus.Executing);

                    string dir = Path.GetDirectoryName(files[i].DestinationPath);
                    if (!Directory.Exists(dir))
                        Directory.CreateDirectory(dir);

                    Stream StreamRead = File.OpenRead(files[i].SourcePath);
                    Stream StreamWriter = File.OpenWrite(files[i].DestinationPath);

                    byte[] buffer = new byte[BufferSize];
                    int lengthRead = 0;
                    long lastReportByte = 0;

                    while ((lengthRead = StreamRead.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        StreamWriter.Write(buffer, 0, lengthRead);
                        CompletedBytes += lengthRead;
                        FileCompletedBytes += lengthRead;

                        int PercReported = (int)(lastReportByte * 100 / files[i].FileSize);
                        int PercNow = (int)(FileCompletedBytes * 100 / files[i].FileSize);

                        if (PercNow > PercReported)
                        {
                            OnProgressReport(this, new ProgressReportArgs()
                            {
                                CompletedBytes = CompletedBytes,
                                CurrentFile = CurrentFile,
                                Destination = files[i].DestinationPath,
                                Source = files[i].SourcePath,
                                FileCompletedBytes = FileCompletedBytes,
                                FileTotalBytes = files[i].FileSize,
                                TotalFiles = files.Count,
                                TotalBytes = TotalSize,
                            });
                            lastReportByte = FileCompletedBytes;
                        }
                    }

                    StreamRead.Close();
                    StreamWriter.Close();
                    files[i].SetStatus(FileStatus.Sucess);
                }
                catch (Exception ex)
                {
                    #region Error
                    files[i].SetStatus(FileStatus.Error);

                    files[i].AddLog("ERROR |Error coping file| ->");
                    files[i].AddLog(ex.ToString());

                    CompletedBytes -= FileCompletedBytes;
                    CompletedBytes += files[i].FileSize;

                    OnError(this, new MultithreadFileCopyErrorArgs()
                    {
                        Error = string.Format("Error coping file: {0} to {1}", files[i].SourcePath, files[i].DestinationPath),
                        Description = ex.ToString(),
                    });
                    #endregion
                }
            }
            #endregion

            OnProcessCompleted(this, files);
        }

        public int BufferSize
        { get; set; }
        public long TotalSize
        {
            get
            {
                long total = 0;
                foreach (var v in files) total += v.FileSize;
                return total;
            }
        }

        public event MultithreadFileCopyErrorHandler OnError;
        public event ProgressReportHandler OnProgressReport;
        public event ProcessCompletedHandler OnProcessCompleted;

        public IEnumerator<MultithreadFile> GetEnumerator()
        { return new MultithreadFileEnumerator(files); }
        IEnumerator IEnumerable.GetEnumerator()
        { return this.GetEnumerator(); }
    }
}
